package com.vkmessenger.net.clients;

import com.google.gson.Gson;
import com.vkmessenger.core.localcache.Cache;
import com.vkmessenger.core.localcache.FriendsCache;
import com.vkmessenger.net.IdentificationData;
import com.vkmessenger.net.handlers.UserResponseHandler;
import com.vkmessenger.net.json.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

/**
 * Created by IntelliJ IDEA.
 * User: Evgeniy Krasko
 * Date: 26.05.12
 * Time: 0:38
 */
public enum WebClient {
    INSTANCE;
    private static Logger logger = Logger.getLogger(WebClient.class.getName());

    private ExecutorService executor = Executors.newSingleThreadExecutor();

    private ThreadLocal<HttpClient> client = new ThreadLocal<HttpClient>() {
        @Override
        protected HttpClient initialValue() {
            return HttpClientFactory.getTunedHttpClient();
        }
    };

    private AtomicReference<AuthResponse> token = new AtomicReference<AuthResponse>();
    private ResponseHandler<FriendRequestsResponse> friendRequestsResponseHandler = new FriendRequestsResponseHandler();
    private ResponseHandler<AuthResponse> authResponseHandler = new AuthResponseHandler();
    private BasicResponseHandler basicResponseHandler = new BasicResponseHandler();
    private Gson gson = new Gson();


    private class AuthResponseHandler implements ResponseHandler<AuthResponse> {
        public AuthResponse handleResponse(HttpResponse response) throws IOException {
            String json = basicResponseHandler.handleResponse(response);
            logger.info(json);
            return gson.fromJson(json, AuthResponse.class);
        }
    }

    private class FriendRequestsResponseHandler implements ResponseHandler<FriendRequestsResponse> {
        public FriendRequestsResponse handleResponse(HttpResponse response) throws IOException {
            String json = basicResponseHandler.handleResponse(response);
            logger.info(json);
            return gson.fromJson(json, FriendRequestsResponse.class);
        }
    }

    public void logIn(final String login, final String password, final Runnable callback) {
        executor.submit(new Runnable() {
            public void run() {
                HttpGet request = new HttpGet(IdentificationData.HTTPS_LOGIN_ADDRESS +
                        "?grant_type=password" +
                        "&client_id=" + IdentificationData.APP_ID +
                        "&client_secret=" + IdentificationData.APP_SECRET +
                        "&username=" + login +
                        "&password=" + password +
                        "&scope=nohttps,messages,friends");
                try {
                    logger.info("Trying to login as " + login);

                    WebClient.this.token.set(client.get().execute(request, authResponseHandler));
                    loadSearchData(null);

                    logger.info("Log in successful, got token " + token.get());

                    callback.run();
                } catch (IOException e) {
                    logger.warning(e.getMessage());
                }
            }
        });
    }

    public boolean isLoggedIn() {
        return token.get() != null;
    }

    public void loadSearchData(final Runnable callback) {
        executor.submit(new Runnable() {
            public void run() {
                Map<String, String> params = new HashMap<String, String>();
                params.put("count", "15");
                params.put("need_messages", "true");
                HttpGet request = getRequest("friends.getRequests", params);
                try {
                    logger.info("Trying load friend requests ");

                    FriendRequestsResponse response = client.get().execute(request, friendRequestsResponseHandler);
                    loadSearchActivityData(response);

                    logger.info("Log in successful, got token " + token.get());

                } catch (IOException e) {
                    logger.warning(e.getMessage());
                }
            }
        });
    }

    public Future<LongPollServerConfig> getLongPollConfig() {
        return executor.submit(new Callable<LongPollServerConfig>() {
            public LongPollServerConfig call() throws Exception {
                HttpGet request = getRequest("messages.getLongPollServer");
                String json = client.get().execute(request, basicResponseHandler);

                logger.info("Server answer: " + json);

                return gson.fromJson(json, LongPollServerConfig.class);
            }
        });
    }

    public Future<Boolean> loadOnlineFriendIds() {
        return executor.submit(new Callable<Boolean>() {
            public Boolean call() throws Exception {
                HttpGet request = getRequest("friends.getOnline");
                try {
                    String json = client.get().execute(request, basicResponseHandler);
                    FriendsIdsResponse response = gson.fromJson(json, FriendsIdsResponse.class);
                    for (long friendId : response.response) {
                        UserResponseHandler.INSTANCE.onUserOnline(friendId);
                    }
                } catch (IOException e) {
                    logger.warning("Can't load online friends");
                    return false;
                }
                return true;
            }
        });
    }

    public Future<FriendsResponse> loadFriends(final Integer count, final String fields, final boolean hints) {
        return executor.submit(new Callable<FriendsResponse>() {
            public FriendsResponse call() throws Exception {
                Map<String, String> params = new HashMap<String, String>(2);
                params.put("fields", fields);
                params.put("order", hints ? "hints" : "name");
                if (count != null) params.put("count", String.valueOf(count));
                HttpGet request = getRequest("friends.get", params);

                String json = client.get().execute(request, basicResponseHandler);
                logger.info("Load friends: " + json);


                FriendsResponse result = gson.fromJson(json, FriendsResponse.class);
                FriendsResponse.FriendResponse[] response = result.response;

                if (hints) {
                    FriendsCache.getInstance().setBestFriends(response);
                } else {
                    FriendsCache.getInstance().setAllFriends(response);
                }
                logger.info("Friends moved to cache.");

                return result;
            }
        });
    }


    /////////Now utility methods////////////////
    private HttpGet getRequest(String method) {
        return getRequest(method, Collections.EMPTY_MAP);
    }

    private HttpGet getRequest(String method, Map<String, String> getParams) {
        return getRequest(method, getParams, Collections.EMPTY_MAP);
    }


    private HttpGet getRequest(String method, Map<String, String> getParams, Map<String, String> postParams) {
        StringBuilder requestString = new StringBuilder("/method/");
        requestString.append(method);

        getParams = new HashMap<String, String>(getParams);
        postParams = new HashMap<String, String>(postParams);

        getParams.put("access_token", token.get().access_token);
        boolean first = true;
        for (Map.Entry<String, String> entry : getParams.entrySet()) {
            requestString.append(first ? '?' : '&');
            requestString.append(entry.getKey());
            requestString.append('=');
            requestString.append(entry.getValue());
            first = false;
        }
        try {
            byte[] bytes = (requestString.toString() + token.get().secret).getBytes();
            requestString.append("&sig=");
            requestString.append(byteToHexString(MessageDigest.getInstance("md5").digest(bytes)));
        } catch (NoSuchAlgorithmException e) {
            logger.severe("No MD5 algorithm, can't establish connection to server");
        }

        logger.info("Making query: " + requestString.toString());

        return new HttpGet(IdentificationData.VK_API_ADDRESS + requestString.toString());
    }


    private static void loadSearchActivityData(FriendRequestsResponse response) {
        Cache.INSTANCE.setFriendRequests(response);
    }

    private static String byteToHexString(byte[] hash) {
        StringBuilder buf = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            if ((b & 0xff) < 0x10) buf.append('0');
            buf.append(Long.toString(b & 0xff, 16));
        }
        return buf.toString();
    }
}
